/*
 *  $Id: ListBox.cs 1232 2010-12-07 13:16:37Z thenn.erannor $
 *  This file is a part of Ragima CCG.
 *  (C) 2008-2010 Ragima development group.
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 *
 */

using System;
using System.Collections.Generic;

using AgateLib.DisplayLib;
using AgateLib.Geometry;

using Nereal.Serialization;

using Ragima.Helper;

namespace Ragima.UI {
    /// <summary>
    /// Список: виджет с несколькими элементами с возможностью их скроллирования.
    /// </summary>
    [TypeId("listbox")]
    public class ListBox : Widget, IScrollable {
        /// <summary>
        /// Экземпляр с пустыми данными списка.
        /// </summary>
        private static readonly IListData Null = new NullListData();

        private const int DefaultPadding = 4;
        private static readonly Color DefaultListBoxBackColor = 0x204040.ToColor();
        private static readonly Color DefaultListBoxHoveredColor = 0x306060.ToColor();

        private IListData _data = Null;
        private int _padding = DefaultPadding;
        private int _position = 0;

        /// <summary>
        /// Данные для списка.
        /// </summary>
        [XmlName("data")]
        public IListData Data {
            get { return _data; }
            set { _data = value ?? Null; }
        }

        /// <summary>
        /// Фоновый цвет пл умолчанию для списка.
        /// </summary>
        protected override Nullable<Color> DefaultBackColor {
            get { return DefaultListBoxBackColor; }
        }

        #region IScrollable implementation
        /// <summary>
        /// Текущая позиция скроллирования.
        /// </summary>
        [NotSerialize]
        public int Position {
            get { return _position; }
            set { _position = Math.Max(Math.Min(value, MaxPosition), 0); }
        }

        /// <summary>
        /// Максимальная позиция скроллирования.
        /// </summary>
        public int MaxPosition {
            get { return Math.Max(_data.ItemCount - VisibleMaxCount, 0); }
        }

        /// <summary>
        /// Скроллирует на указанное смещение.
        /// </summary>
        public void ScrollBy(int offset) {
            Position += offset;
        }

        /// <summary>
        /// Скроллирует на указанную позицию.
        /// </summary>
        public void ScrollTo(int index) {
            Position = index;
        }
        #endregion

        /// <summary>
        /// Внутренний отступ.
        /// </summary>
        [XmlName("padding"), DefaultValue(DefaultPadding)]
        public int Padding {
            get { return _padding; }
            set { _padding = Math.Max(value, 0); }
        }

        /// <summary>
        /// Начальная точка расположения содержимого списка.
        /// </summary>
        private Point ContentLocation {
            get { return AbsoluteLocation.Padding(_padding); }
        }

        /// <summary>
        /// Ширина содержимого списка.
        /// </summary>
        private int ContentWidth {
            get { return Width - 2 * _padding; }
        }
        /// <summary>
        /// Высота содержимого списка.
        /// </summary>
        private int ContentHeight {
            get { return Height - 2 * _padding; }
        }

        /// <summary>
        /// Область содержимого списка.
        /// </summary>
        private Rectangle ContentBounds {
            get { return AbsoluteBounds.Padding(_padding); }
        }

        /// <summary>
        /// Максимальное число видимых элементов.
        /// </summary>
        private int VisibleMaxCount {
            get { return ContentHeight / _data.GetItemHeight(this); }
        }

        /// <summary>
        /// Текущее число видимых элементов.
        /// </summary>
        private int VisibleItemCount {
            get { return Math.Min(_data.ItemCount - _position, VisibleMaxCount); }
        }

        /// <summary>
        /// Получает область для указанного элемента.
        /// </summary>
        private Rectangle GetLineRect(int index) {
            if (index < 0 || index >= VisibleMaxCount)
                return Rectangle.Empty;
            int height = _data.GetItemHeight(this);
            var bounds = ContentBounds;
            bounds.Y += index * height;
            bounds.Height = height;
            return bounds;
        }

        /// <summary>
        /// Получает номер элемента под курсором мыши.
        /// </summary>
        /// <returns>-1, если курсор не на элемента списка, иначе номер элемента.</returns>
        private int GetFocusIndex() {
            var pos = MouseLocation.Padding(-_padding);
            if (pos.X < 0 || pos.Y < 0 || pos.X >= ContentWidth)
                return -1;
            int index = pos.Y / _data.GetItemHeight(this);
            return index < VisibleItemCount ? index + _position : -1;
        }

        /// <summary>
        /// Рисует список.
        /// </summary>
        public override void Draw() {
            DrawBackground();
            DrawHovered();
            DrawLines();
        }

        /// <summary>
        /// Рисует подсветку для элемента под курсором мыши.
        /// </summary>
        protected void DrawHovered() {
            if (!Hovered)
                return;
            int index = GetFocusIndex();
            if (index == -1)
                return;
            Display.FillRect(GetLineRect(index - _position), DefaultListBoxHoveredColor);
        }

        /// <summary>
        /// Рисует все видимые элементы.
        /// </summary>
        protected void DrawLines() {
            int height = _data.GetItemHeight(this);
            var rect = ContentBounds;
            rect.Height = height;
            int count = VisibleItemCount;
            for (int i = _position; i < _position + count; i++) {
                _data.DrawItem(this, i, rect);
                rect.Y += height;
            }
        }

        /// <summary>
        /// Заглушка для данных списка, представляющая собой пустой список.
        /// </summary>
        private class NullListData : IListData {
            #region IListData implementation
            int IListData.ItemCount {
                get { return 0; }
            }

            int IListData.GetItemHeight(Widget widget) {
                return widget.Height;
            }

            void IListData.DrawItem(Widget widget, int itemIndex, Rectangle itemRect) {
            }
            #endregion
        }
    }

    /// <summary>
    /// Список текстовых строк.
    /// </summary>
    [TypeId("text")]
    public class TextListData : IListData {
        private List<string> _lines;

        public TextListData() {
            _lines = new List<string>();
        }

        /// <summary>
        /// Данные списка: строки.
        /// </summary>
        [XmlName(null, "s")]
        public List<string> Lines {
            get { return _lines; }
        }

        #region IListData implementation
        int IListData.ItemCount {
            get { return _lines.Count; }
        }

        int IListData.GetItemHeight(Widget widget) {
            return widget.Font.FontSurface.FontHeight + 2;
        }

        void IListData.DrawItem(Widget widget, int itemIndex, Rectangle itemRect) {
            if (itemIndex >= 0 && itemIndex < _lines.Count) {
                widget.Font.Color = widget.MainColor ?? Color.White;
                widget.Font.Draw(itemRect.Left, itemRect.Top + 1, _lines[itemIndex]);
            }
        }
        #endregion
    }
}
